قدرت ماژول ast پایتون را برای دستکاری درخت نحو انتزاعی بررسی کنید. با آنالیز، اصلاح و تولید برنامهنویسی کد پایتون آشنا شوید.
ماژول Python Ast: دستکاری درخت نحو انتزاعی (AST) سادهسازی شده
ماژول ast
پایتون، راهی قدرتمند برای تعامل با درخت نحو انتزاعی (AST) کد پایتون ارائه میدهد. AST یک نمایش درختی از ساختار نحوی کد منبع است که امکان تجزیه و تحلیل، اصلاح و حتی تولید برنامهنویسی کد پایتون را فراهم میکند. این امر، درهای مختلفی را به روی برنامههای کاربردی از جمله ابزارهای تجزیه و تحلیل کد، بازسازی خودکار، تحلیل ایستا و حتی افزونههای زبان سفارشی باز میکند. این مقاله شما را از طریق اصول اولیه ماژول ast
راهنمایی میکند، نمونههای عملی و بینشهایی را در مورد قابلیتهای آن ارائه میدهد.
درخت نحو انتزاعی (AST) چیست؟
قبل از ورود به ماژول ast
، بیایید بفهمیم درخت نحو انتزاعی چیست. هنگامی که یک مفسر پایتون کد شما را اجرا میکند، اولین قدم، تجزیه کد به یک AST است. این ساختار درختی، عناصر نحوی کد را نشان میدهد، مانند توابع، کلاسها، حلقهها، عبارات و عملگرها، همراه با روابط آنها. AST جزئیات نامربوطی مانند فضای خالی و نظرات را دور میاندازد و بر اطلاعات ساختاری اساسی تمرکز میکند. با نمایش کد به این روش، این امکان برای برنامهها فراهم میشود تا خود کد را تجزیه و تحلیل و دستکاری کنند، که در بسیاری از موقعیتها بسیار مفید است.
شروع کار با ماژول ast
ماژول ast
بخشی از کتابخانه استاندارد پایتون است، بنابراین نیازی به نصب هیچ بستهای اضافی ندارید. به سادگی آن را وارد کنید تا استفاده از آن شروع شود:
import ast
عملکرد اصلی ماژول ast
، ast.parse()
است، که یک رشته از کد پایتون را به عنوان ورودی دریافت میکند و یک شی AST را برمیگرداند.
code = """
def add(x, y):
return x + y
"""
ast_tree = ast.parse(code)
print(ast_tree)
این خروجی چیزی شبیه به: <_ast.Module object at 0x...>
خواهد بود. در حالی که این خروجی اطلاعات خاصی را ارائه نمیدهد، نشان میدهد که کد با موفقیت به یک AST تجزیه شده است. شی ast_tree
اکنون کل ساختار کد تجزیه شده را شامل میشود.
بررسی AST
برای درک ساختار AST، میتوانیم از تابع ast.dump()
استفاده کنیم. این تابع به صورت بازگشتی از درخت عبور میکند و یک نمایش دقیق از هر گره را چاپ میکند.
code = """
def add(x, y):
return x + y
"""
ast_tree = ast.parse(code)
print(ast.dump(ast_tree, indent=4))
خروجی به این صورت خواهد بود:
Module(
body=[
FunctionDef(
name='add',
args=arguments(
posonlyargs=[],
args=[
arg(arg='x', annotation=None, type_comment=None),
arg(arg='y', annotation=None, type_comment=None)
],
kwonlyargs=[],
kw_defaults=[],
defaults=[]
),
body=[
Return(
value=BinOp(
left=Name(id='x', ctx=Load()),
op=Add(),
right=Name(id='y', ctx=Load())
)
)
],
decorator_list=[],
returns=None,
type_comment=None
)
],
type_ignores=[]
)
این خروجی ساختار سلسله مراتبی کد را نشان میدهد. بیایید آن را تجزیه کنیم:
Module
: گره ریشه که کل ماژول را نشان میدهد.body
: لیستی از عبارات در داخل ماژول.FunctionDef
: نشاندهنده تعریف تابع است. ویژگیهای آن عبارتند از:name
: نام تابع ('add').args
: آرگومانهای تابع.arguments
: حاوی اطلاعاتی در مورد آرگومانهای تابع است.arg
: یک آرگومان واحد را نشان میدهد (به عنوان مثال، 'x'، 'y').body
: بدنه تابع (لیستی از عبارات).Return
: یک عبارت بازگشت را نشان میدهد.value
: مقداری که برگردانده میشود.BinOp
: یک عملگر باینری را نشان میدهد (به عنوان مثال، x + y).left
: عملوند سمت چپ (به عنوان مثال، 'x').op
: عملگر (به عنوان مثال، 'Add').right
: عملوند سمت راست (به عنوان مثال، 'y').
پیمایش AST
ماژول ast
کلاس ast.NodeVisitor
را برای پیمایش AST فراهم میکند. با زیر کلاسبندی ast.NodeVisitor
و بازنویسی متدهای آن، میتوانید انواع گرههای خاص را هنگام مواجهه در طول پیمایش، پردازش کنید. این برای تجزیه و تحلیل ساختار کد، شناسایی الگوهای خاص یا استخراج اطلاعات مفید است.
import ast
class FunctionNameExtractor(ast.NodeVisitor):
def __init__(self):
self.function_names = []
def visit_FunctionDef(self, node):
self.function_names.append(node.name)
code = """
def add(x, y):
return x + y
def subtract(x, y):
return x - y
"""
ast_tree = ast.parse(code)
extractor = FunctionNameExtractor()
extractor.visit(ast_tree)
print(extractor.function_names) # Output: ['add', 'subtract']
در این مثال، FunctionNameExtractor
از ast.NodeVisitor
به ارث میرسد و متد visit_FunctionDef
را بازنویسی میکند. این متد برای هر گره تعریف تابع در AST فراخوانی میشود. این متد نام تابع را به لیست function_names
اضافه میکند. متد visit()
پیمایش AST را آغاز میکند.
مثال: یافتن تمام انتسابهای متغیر
import ast
class VariableAssignmentFinder(ast.NodeVisitor):
def __init__(self):
self.assignments = []
def visit_Assign(self, node):
for target in node.targets:
if isinstance(target, ast.Name):
self.assignments.append(target.id)
code = """
x = 10
y = x + 5
message = "hello"
"""
ast_tree = ast.parse(code)
finder = VariableAssignmentFinder()
finder.visit(ast_tree)
print(finder.assignments) # Output: ['x', 'y', 'message']
این مثال تمام انتسابهای متغیر را در کد پیدا میکند. متد visit_Assign
برای هر عبارت انتساب فراخوانی میشود. این متد، از طریق اهداف انتساب تکرار میشود و اگر یک هدف یک نام ساده (ast.Name
) باشد، نام را به لیست assignments
اضافه میکند.
اصلاح AST
ماژول ast
همچنین به شما امکان میدهد AST را اصلاح کنید. میتوانید گرههای موجود را تغییر دهید، گرههای جدیدی اضافه کنید یا گرهها را بهطور کلی حذف کنید. برای اصلاح AST، از کلاس ast.NodeTransformer
استفاده میکنید. مشابه ast.NodeVisitor
، شما ast.NodeTransformer
را زیر کلاس میکنید و متدهای آن را برای اصلاح انواع گرههای خاص بازنویسی میکنید. تفاوت اصلی این است که متدهای ast.NodeTransformer
باید گره اصلاح شده (یا یک گره جدید برای جایگزینی آن) را برگردانند. اگر یک متد None
را برگرداند، گره از AST حذف میشود.
پس از اصلاح AST، باید آن را با استفاده از تابع compile()
دوباره به کد پایتون قابل اجرا کامپایل کنید.
import ast
class AddOneTransformer(ast.NodeTransformer):
def visit_Num(self, node):
return ast.Num(n=node.n + 1)
code = """
x = 10
y = 20
"""
ast_tree = ast.parse(code)
transformer = AddOneTransformer()
new_ast_tree = transformer.visit(ast_tree)
new_code = compile(new_ast_tree, '<string>', 'exec')
# Execute the modified code
exec(new_code)
print(x) # Output: 11
print(y) # Output: 21
در این مثال، AddOneTransformer
از ast.NodeTransformer
به ارث میرسد و متد visit_Num
را بازنویسی میکند. این متد برای هر گره literal عددی (ast.Num
) فراخوانی میشود. این متد یک گره ast.Num
جدید با مقدار افزایشیافته توسط 1 ایجاد میکند. متد visit()
، AST اصلاحشده را برمیگرداند.
تابع compile()
، AST اصلاحشده، یک نام فایل (<string>
را در این مورد، که نشان میدهد کد از یک رشته میآید) و یک حالت اجرا ('exec'
برای اجرای یک بلوک کد) را میگیرد. این یک شی کد را برمیگرداند که میتوان با استفاده از تابع exec()
اجرا کرد.
مثال: جایگزینی نام متغیر
import ast
class VariableNameReplacer(ast.NodeTransformer):
def __init__(self, old_name, new_name):
self.old_name = old_name
self.new_name = new_name
def visit_Name(self, node):
if node.id == self.old_name:
return ast.Name(id=self.new_name, ctx=node.ctx)
return node
code = """
def multiply_by_two(number):
return number * 2
result = multiply_by_two(5)
print(result)
"""
ast_tree = ast.parse(code)
replacer = VariableNameReplacer('number', 'num')
new_ast_tree = replacer.visit(ast_tree)
new_code = compile(new_ast_tree, '<string>', 'exec')
# Execute the modified code
exec(new_code)
این مثال تمام موارد نام متغیر 'number'
را با 'num'
جایگزین میکند. VariableNameReplacer
نامهای قدیمی و جدید را به عنوان آرگومان میگیرد. متد visit_Name
برای هر گره نام فراخوانی میشود. اگر شناسه گره با نام قدیمی مطابقت داشته باشد، یک گره ast.Name
جدید با نام جدید و همان زمینه (node.ctx
) ایجاد میکند. زمینه نشان میدهد که چگونه از نام استفاده میشود (به عنوان مثال، بارگیری، ذخیرهسازی).
تولید کد از یک AST
در حالی که compile()
به شما امکان میدهد کد را از یک AST اجرا کنید، اما راهی برای دریافت کد به عنوان یک رشته ارائه نمیدهد. برای تولید کد پایتون از یک AST، میتوانید از کتابخانه astunparse
استفاده کنید. این کتابخانه بخشی از کتابخانه استاندارد نیست، بنابراین ابتدا باید آن را نصب کنید:
pip install astunparse
سپس، میتوانید از تابع astunparse.unparse()
برای تولید کد از یک AST استفاده کنید.
import ast
import astunparse
code = """
def add(x, y):
return x + y
"""
ast_tree = ast.parse(code)
generated_code = astunparse.unparse(ast_tree)
print(generated_code)
خروجی به این صورت خواهد بود:
def add(x, y):
return (x + y)
توجه: پرانتزها در اطراف (x + y)
توسط astunparse
اضافه شدهاند تا تقدم صحیح عملگر را تضمین کنند. این پرانتزها ممکن است لزوماً ضروری نباشند، اما صحت کد را تضمین میکنند.
مثال: تولید یک کلاس ساده
import ast
import astunparse
class_name = 'MyClass'
method_name = 'my_method'
# Create the class definition node
class_def = ast.ClassDef(
name=class_name,
bases=[],
keywords=[],
body=[
ast.FunctionDef(
name=method_name,
args=ast.arguments(
posonlyargs=[],
args=[],
kwonlyargs=[],
kw_defaults=[],
defaults=[]
),
body=[
ast.Pass()
],
decorator_list=[],
returns=None,
type_comment=None
)
],
decorator_list=[]
)
# Create the module node containing the class definition
module = ast.Module(body=[class_def], type_ignores=[])
# Generate the code
code = astunparse.unparse(module)
print(code)
این مثال کد پایتون زیر را تولید میکند:
class MyClass:
def my_method():
pass
این نحوه ساختن یک AST از ابتدا و سپس تولید کد از آن را نشان میدهد. این رویکرد برای ابزارهای تولید کد و متا برنامهنویسی قدرتمند است.
برنامههای کاربردی عملی ماژول ast
ماژول ast
دارای کاربردهای عملی متعددی است، از جمله:
- تجزیه و تحلیل کد: تجزیه و تحلیل کد برای تخلفات سبک، آسیبپذیریهای امنیتی یا گلوگاههای عملکرد. به عنوان مثال، میتوانید ابزاری برای اعمال استانداردهای کدنویسی در یک پروژه بزرگ بنویسید.
- بازسازی خودکار: خودکارسازی کارهایی مانند تغییر نام متغیرها، استخراج متدها یا تبدیل کد برای استفاده از ویژگیهای جدیدتر زبان. ابزارهایی مانند
rope
از ASTها برای قابلیتهای بازسازی قدرتمند استفاده میکنند. - تجزیه و تحلیل ایستا: شناسایی خطاهای احتمالی یا اشکالات در کد بدون اجرای واقعی آن. ابزارهایی مانند
pylint
وflake8
از تجزیه و تحلیل AST برای تشخیص مشکلات استفاده میکنند. - تولید کد: تولید خودکار کد بر اساس الگوها یا مشخصات. این برای ایجاد کد تکراری یا تولید کد برای پلتفرمهای مختلف مفید است.
- افزونههای زبان: ایجاد افزونههای زبان سفارشی یا زبانهای خاص دامنه (DSL) با تبدیل کد پایتون به نمایشهای مختلف.
- حسابرسی امنیتی: تجزیه و تحلیل کد برای ساختارهای بالقوه مضر یا آسیبپذیریها. این میتواند برای شناسایی شیوههای کدنویسی ناامن استفاده شود.
مثال: اعمال سبک کدنویسی
بیایید بگوییم میخواهید اطمینان حاصل کنید که تمام نامهای تابع در پروژه شما از قرارداد snake_case (به عنوان مثال، my_function
به جای myFunction
) پیروی میکنند. میتوانید از ماژول ast
برای بررسی تخلفات استفاده کنید.
import ast
import re
class SnakeCaseChecker(ast.NodeVisitor):
def __init__(self):
self.errors = []
def visit_FunctionDef(self, node):
if not re.match(r'^[a-z]+(_[a-z]+)*$', node.name):
self.errors.append(f"Function name '{node.name}' does not follow snake_case convention")
def check_code(self, code):
ast_tree = ast.parse(code)
self.visit(ast_tree)
return self.errors
# Example usage
code = """
def myFunction(x):
return x * 2
def calculate_area(width, height):
return width * height
"""
checker = SnakeCaseChecker()
errors = checker.check_code(code)
if errors:
for error in errors:
print(error)
else:
print("No style violations found")
این کد یک کلاس SnakeCaseChecker
تعریف میکند که از ast.NodeVisitor
به ارث میرسد. متد visit_FunctionDef
بررسی میکند که آیا نام تابع با عبارت منظم snake_case مطابقت دارد یا خیر. اگر نه، یک پیام خطا به لیست errors
اضافه میکند. متد check_code
کد را تجزیه میکند، از AST عبور میکند و لیست خطاها را برمیگرداند.
بهترین شیوهها هنگام کار با ماژول ast
- ساختار AST را درک کنید: قبل از تلاش برای دستکاری AST، وقت بگذارید تا ساختار آن را با استفاده از
ast.dump()
درک کنید. این به شما کمک میکند تا گرههایی را که باید با آنها کار کنید، شناسایی کنید. - از
ast.NodeVisitor
وast.NodeTransformer
استفاده کنید: این کلاسها راهی مناسب برای پیمایش و اصلاح AST بدون نیاز به پیمایش دستی درخت ارائه میدهند. - به طور کامل تست کنید: هنگام اصلاح AST، کد خود را به طور کامل تست کنید تا مطمئن شوید که تغییرات صحیح هستند و هیچ خطایی ایجاد نمیکنند.
astunparse
را برای تولید کد در نظر بگیرید: در حالی کهcompile()
برای اجرای کد اصلاحشده مفید است،astunparse
راهی برای تولید کد پایتون قابل خواندن از یک AST ارائه میدهد.- از نکات نوع استفاده کنید: نکات نوع میتوانند خوانایی و قابلیت نگهداری کد شما را به طور قابل توجهی بهبود بخشند، به خصوص هنگام کار با ساختارهای AST پیچیده.
- کد خود را مستند کنید: هنگام ایجاد بازدیدکنندهها یا ترانسفورماتورهای AST سفارشی، کد خود را به وضوح مستند کنید تا هدف هر متد و تغییراتی را که در AST ایجاد میکند، توضیح دهید.
چالشها و ملاحظات
- پیچیدگی: کار با ASTها میتواند پیچیده باشد، به خصوص برای پایگاههای کد بزرگتر. درک انواع گرههای مختلف و روابط آنها میتواند چالشبرانگیز باشد.
- نگهداری: ساختارهای AST میتوانند بین نسخههای پایتون تغییر کنند. مطمئن شوید که کد خود را با نسخههای مختلف پایتون تست میکنید تا از سازگاری اطمینان حاصل کنید.
- عملکرد: پیمایش و اصلاح ASTهای بزرگ میتواند کند باشد. عملکرد خود را برای بهبود عملکرد در نظر بگیرید. ذخیرهسازی گرههای پرکاربرد یا استفاده از الگوریتمهای کارآمدتر میتواند کمک کند.
- مدیریت خطا: هنگام تجزیه یا دستکاری AST، خطاها را به خوبی مدیریت کنید. پیامهای خطا آموزندهای را به کاربر ارائه دهید.
- امنیت: هنگام اجرای کد تولید شده از یک AST، به خصوص اگر AST بر اساس ورودی کاربر باشد، مراقب باشید. ورودی را برای جلوگیری از حملات تزریق کد، ضدعفونی کنید.
نتیجهگیری
ماژول ast
پایتون، راهی قدرتمند و انعطافپذیر برای تعامل با درخت نحو انتزاعی کد پایتون ارائه میدهد. با درک ساختار AST و استفاده از کلاسهای ast.NodeVisitor
و ast.NodeTransformer
، میتوانید کد پایتون را به صورت برنامهنویسی تجزیه و تحلیل، اصلاح و تولید کنید. این امر، درهای مختلفی را به روی طیف گستردهای از برنامهها، از ابزارهای تجزیه و تحلیل کد گرفته تا بازسازی خودکار و حتی افزونههای زبان سفارشی، باز میکند. در حالی که کار با ASTها میتواند پیچیده باشد، مزایای توانایی دستکاری برنامهنویسی کد قابل توجه است. قدرت ماژول ast
را برای باز کردن امکانات جدید در پروژههای پایتون خود در آغوش بگیرید.